package com.zxy.ziems.server.monitor.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.zxy.ziems.server.alarm.pojo.dto.AlarmRecordEvent;
import com.zxy.ziems.server.alarm.service.AlarmRecordService;
import com.zxy.ziems.server.constant.Constant;
import com.zxy.ziems.server.device.pojo.entity.DeviceEntity;
import com.zxy.ziems.server.device.service.DeviceService;
import com.zxy.ziems.server.monitor.convertor.MonitorConvertor;
import com.zxy.ziems.server.monitor.dao.MonitorDeviceMapper;
import com.zxy.ziems.server.monitor.hik.HikEventNotification;
import com.zxy.ziems.server.monitor.pojo.constants.MonitorConstant;
import com.zxy.ziems.server.monitor.pojo.entity.MonitorDeviceEntity;
import com.zxy.ziems.server.monitor.pojo.hik.HikMonitorDevice;
import com.zxy.ziems.server.monitor.pojo.hik.HikPreviewURL;
import com.zxy.ziems.server.monitor.pojo.hik.MonitorCameraPreview;
import com.zxy.ziems.server.monitor.pojo.hik.MonitorCameraPreviewParam;
import com.zxy.ziems.server.monitor.pojo.vo.MonitorDeviceOldVO;
import com.zxy.ziems.server.monitor.pojo.vo.MonitorDeviceVO;
import com.zxy.ziems.server.monitor.service.MonitorDeviceService;
import com.zxy.ziems.server.monitor.utils.HikPostApiUtils;
import com.zxy.ziems.server.mybatis.core.pojo.PageParam;
import com.zxy.ziems.server.mybatis.core.query.WrapperX;
import com.zxy.ziems.server.mybatis.core.service.ServiceImplX;
import com.zxy.ziems.server.utils.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 监控设备
 *
 * @author chdzq
 * @create 2023/5/24
 */
@Slf4j
@Service
public class MonitorDeviceServiceImpl extends ServiceImplX<MonitorDeviceMapper, MonitorDeviceEntity>
        implements MonitorDeviceService {

    private String typeCode = "Monitor";

    @Override
    @Transactional(
            rollbackFor = Exception.class
    )
    public void synchronizeDevices() {
        Integer pageNo = 1;
        Integer pageSize = 500;

        while (pageNo > 0) {
            PageParam param = new PageParam();
            param.setPageNo(pageNo);
            param.setPageSize(pageSize);
            List<HikMonitorDevice> hikDevices = HikPostApiUtils.getPageListPostApiBy("/api/resource/v1/cameras", param,
                    HikMonitorDevice.class);

            List<MonitorDeviceEntity> monitorDevice =
                    hikDevices.stream().map(HikMonitorDevice::makeBy).collect(Collectors.toList());
            Set<String> idSet = monitorDevice.stream().map(MonitorDeviceEntity::getDeviceUid).collect(Collectors.toSet());
            if (CollectionUtil.isNotEmpty(idSet)) {
                update(WrapperX.<MonitorDeviceEntity>lambdaUpdate()
                        .in(MonitorDeviceEntity::getDeviceUid, idSet)
                        .set(MonitorDeviceEntity::getDeleteStatus, Constant.NOT_DELETED));
                saveOrUpdateBatch(monitorDevice, MonitorDeviceEntity::getDeviceUid);

                List<DeviceEntity> deviceList = monitorDevice.stream()
                        .map((a0) -> {
                            String deviceType = a0.getDeviceType();
                            DeviceEntity a1 = new DeviceEntity();
                            a1.setCode(a0.getDeviceUid());
                            a1.setName(a0.getDeviceName());
                            a1.setTypeCode(deviceType);
//                            0-不在线，1-在线
                            a1.setOnlineStatus(a0.getStatus());
                            return a1;
                        })
                        .toList();
                deviceService.batchSaveOrUpdate(deviceList, MonitorConstant.DEVICE_TYPE);
            }
            if (monitorDevice.size() < pageSize) {
                pageNo = 0;
            } else {
                pageNo = pageNo + 1;
            }
        }
    }

    @Override
    public void synchronizeDeviceStatus() {

    }

    @Override
    public MonitorCameraPreview getPreview(MonitorCameraPreviewParam param) {

        AssertUtil.isFalse(null == param || StrUtil.isEmpty(param.getDeviceId()), "查询参数不对");

        DeviceEntity entity = deviceService.getById(param.getDeviceId());
        AssertUtil.isFalse(null == entity, "查询的设备不存在");

        /**
         * cameraIndexCode	body	string	true	监控点唯一标识，#API@分页获取监控点资源@#接口获取返回参数cameraIndexCode
         * streamType	body	integer	false	码流类型，0:主码流
         * 1:子码流
         * 2:第三码流
         * 参数不填，默认为主码流
         * protocol	body	string	false	取流协议（应用层协议），“hik”:HIK私有协议，使用视频SDK进行播放时，传入此类型；“rtsp”:RTSP协议；“rtmp”:RTMP
         * 协议；“hls”:HLS协议（HLS协议只支持海康SDK协议、EHOME协议、ONVIF协议接入的设备；只支持H264视频编码和AAC音频编码）。参数不填，默认为HIK协议
         * transmode	body	integer	false	传输协议（传输层协议），0:UDP
         * 1:TCP
         *       默认是TCP
         *         注：
         *         GB28181 2011及以前版本只支持UDP传输
         *         expand	body	string	false	标识扩展内容，格式：key=value，
         *         调用方根据其播放控件支持的解码格式选择相应的封装类型；
         *         支持的内容详见附录F expand扩展内容说明
         *         streamform	body	string	false	输出码流转封装格式，“ps”:PS封装格式、“rtp”:RTP封装协议。当protocol=rtsp时生效，且不传值时默认为RTP封装协议。
         */
        String protocol = StrUtil.isBlank(param.getProtocol()) ? "rtsp" : param.getProtocol();
        HashMap<String, Object> hikParam = new HashMap<>(6);

        hikParam.put("streamType", 1);
        hikParam.put("protocol", protocol);
        hikParam.put("transmode", 1);
        if (protocol.equals("hls")) {
            hikParam.put("expand", "transcode=1&videotype=h264&audiotype=AAC");
        } else if (protocol.equals("rtsp")) {
            hikParam.put("expand", "streamform=rtp");
        }
        hikParam.put("cameraIndexCode", entity.getCode());
        HikPreviewURL previewURL = HikPostApiUtils.getPostApiBy("/api/video/v2/cameras/previewURLs", hikParam,
                HikPreviewURL.class);

        if (StrUtil.isNotEmpty(previewURL.getUrl())){
            previewURL.setUrl(previewURL.getUrl().replace(
                    "http://192.0.0.75:83",
                    "http://172.16.70.158/hik"));
        }
        MonitorCameraPreview preview = new MonitorCameraPreview();
        preview.setDeviceId(entity.getId());
        preview.setProtocol(protocol);
        preview.setUrl(previewURL.getUrl());
        return preview;
    }

    static HashMap<Integer, String> CODE_MESSAGE;

    static {
        /**
         * 参数名称	参数位置	数据类型	是否必须	参数描述
         * eventTypes	body	integer[]	true	事件类型,参考附录D 事件列表
         * eventDest	body	string	true	指定事件接收的地址，采用restful回调模式，支持http和https，样式如下：http://ip:port/eventRcv或者
         * https://ip:port/eventRcv
         * 不超过1024个字符，事件接收地址由应用方负责按指定的规范提供，报文格式如附录D.3事件报文格式，事件接收接口不需要认证
         * subType	body	integer	false	订阅类型 0：订阅原始事件 1：联动事件 2：原始事件和联动事件，不填使用默认值0
         * eventLvl	body	integer[]	false	事件等级，订阅类型为0时，此参数无效，使用默认值0
         * 在订阅类型为1时，不填使用默认值[1,2,3]
         * 在订阅类型为2时，不填使用默认值[0,1,2,3]
         * 数组大小不超过32，事件等级大小不超过31
         * @link https://open.hikvision.com/docs/docId?productId=618cf0a9ec4acb67a0a10fd6&version=%2F3711510d6ee846c8a1ece880a4615932&tagPath=API%E5%88%97%E8%A1%A8-%E4%BA%8B%E4%BB%B6%E6%9C%8D%E5%8A%A1#adebe5ee
         * -----------------------------
         * 视频监控
         * 事件分类	事件名称	事件类型码
         * 视频丢失	131329
         * 视频遮挡	131330
         * 移动侦测	131331
         * 场景变更	131612
         * 虚焦	131613
         * 报警输入	589825
         * 可视域事件	196355
         * GPS采集	851969
         * 区域入侵	131588
         * 越界侦测	131585
         * 进入区域	131586
         * 离开区域	131587
         * 徘徊侦测	131590
         * 人员聚集	131593
         * 快速移动	131592
         * 停车侦测	131591
         * 物品遗留	131594
         * 物品拿取	131595
         * 人数异常	131664
         * 间距异常	131665
         * 剧烈运动	131596
         * 离岗	131603
         * 倒地	131605
         * 攀高	131597
         * 重点人员起身	131610
         * 如厕超时	131608
         * 人员站立	131666
         * 静坐	131667
         * 防风场滞留	131609
         * 起身	131598
         * 人靠近ATM	131599
         * 操作超时	131600
         * 贴纸条	131601
         * 安装读卡器	131602
         * 尾随	131604
         * 声强突变	131606
         * 折线攀高	131607
         * 折线警戒面	131611
         * 温差报警	192518
         * 温度报警	192517
         * 船只检测	192516
         * 火点检测	192515
         * 烟火检测	192514
         * 烟雾检测	192513
         * 视频网管
         * 监控点离线	889196545
         */
        CODE_MESSAGE = new HashMap<>();
        CODE_MESSAGE.put(131329, "视频丢失");
        CODE_MESSAGE.put(131330, "视频遮挡");
        CODE_MESSAGE.put(131331, "移动侦测");
        CODE_MESSAGE.put(131612, "场景变更");
        CODE_MESSAGE.put(131613, "虚焦");
        CODE_MESSAGE.put(589825, "报警输入");
        CODE_MESSAGE.put(196355, "可视域事件");
        CODE_MESSAGE.put(851969, "GPS采集");
        CODE_MESSAGE.put(131588, "区域入侵");
        CODE_MESSAGE.put(131585, "越界侦测");
        CODE_MESSAGE.put(131586, "进入区域");
        CODE_MESSAGE.put(131587, "离开区域");
        CODE_MESSAGE.put(131590, "徘徊侦测");
        CODE_MESSAGE.put(131593, "人员聚集");
        CODE_MESSAGE.put(131592, "快速移动");
        CODE_MESSAGE.put(131591, "停车侦测");
        CODE_MESSAGE.put(131594, "物品遗留");
        CODE_MESSAGE.put(131595, "物品拿取");
        CODE_MESSAGE.put(131664, "人数异常");
        CODE_MESSAGE.put(131665, "间距异常");
        CODE_MESSAGE.put(131596, "剧烈运动");
        CODE_MESSAGE.put(131603, "离岗");
        CODE_MESSAGE.put(131605, "倒地");
        CODE_MESSAGE.put(131597, "攀高");
        CODE_MESSAGE.put(131610, "重点人员起身");
        CODE_MESSAGE.put(131608, "如厕超时");
        CODE_MESSAGE.put(131666, "人员站立");
        CODE_MESSAGE.put(131667, "静坐");
        CODE_MESSAGE.put(131609, "防风场滞留");
        CODE_MESSAGE.put(131598, "起身");
        CODE_MESSAGE.put(131599, "人靠近ATM");
        CODE_MESSAGE.put(131600, "操作超时");
        CODE_MESSAGE.put(131601, "贴纸条");
        CODE_MESSAGE.put(131602, "安装读卡器");
        CODE_MESSAGE.put(131604, "尾随");
        CODE_MESSAGE.put(131606, "声强突变");
        CODE_MESSAGE.put(131607, "折线攀高");
        CODE_MESSAGE.put(131611, "折线警戒面");
        CODE_MESSAGE.put(192518, "温差报警");
        CODE_MESSAGE.put(192517, "温度报警");
        CODE_MESSAGE.put(192516, "船只");
        CODE_MESSAGE.put(192515, "火点");
        CODE_MESSAGE.put(192514, "烟火");
        CODE_MESSAGE.put(192513, "烟雾");
        CODE_MESSAGE.put(192513, "视频网管");
        CODE_MESSAGE.put(889196545, "离线");
    }

    @Override
    @Transactional(
            rollbackFor = Exception.class
    )
    public void processUploadEventFromHik(HikEventNotification notification) {
        log.debug("[海康上报事件]:{}", notification);
        if (null == notification ||
                !Objects.equals("OnEventNotify", notification.getMethod()) ||
                null == notification.getParams() ||
                CollectionUtil.isEmpty(notification.getParams().getEvents())) {
            return;
        }
        for (HikEventNotification.Event e : notification.getParams().getEvents()) {
            //移动侦测报
            if (!Objects.equals(1, e.getStatus())) {
                continue;
            }

            DeviceEntity entity = deviceService.getByCode(e.getSrcIndex(), MonitorConstant.DEVICE_TYPE);
            if (null == entity) {
                continue;
            }
            AlarmRecordEvent event = AlarmRecordEvent.builder()
                    .deviceCode(e.getSrcIndex())
                    .alarmTime(e.getHappenTime())
                    .alarmContent(e.getSrcName() + "监控侦测到" + CODE_MESSAGE.getOrDefault(e.getEventType(), "未知事件"))
                    .deviceId(entity.getId())
                    .deviceCode(MonitorConstant.DEVICE_TYPE)
                    .build();
            alarmRecordService.add(event);
        }
    }

    @Override
    public void subscriptionEvent() {
        HashMap<String, Object> param = new HashMap<>(3);
        param.put("eventTypes", new ArrayList<>(CODE_MESSAGE.keySet()));
        param.put("eventDest", "http://172.16.70.58/ziems/api/monitor/hik/event");
        param.put("subType", 0);
        HikPostApiUtils.getPostApiBy("/api/eventService/v1/eventSubscriptionByEventTypes", param, Object.class);
    }


    @Override
    public List<MonitorDeviceEntity> getListDo() {
        return this.list();
    }


    @Override
    public List<MonitorDeviceOldVO> getList() {
        final List<DeviceEntity> deviceEntityList = deviceService.listByTypeCode(typeCode);


        List<MonitorDeviceVO> monitorDeviceVOList = listAll();
        Map<String, MonitorDeviceVO> map = monitorDeviceVOList.stream().collect(Collectors.toMap(m -> m.getDeviceUid(), m -> m));


        List<MonitorDeviceOldVO> collect = deviceEntityList.stream().map(m -> {
            final MonitorDeviceVO monitorDeviceVO = map.get(m.getCode());
            MonitorDeviceOldVO monitorDeviceOldVO = MonitorConvertor.INSTANCE.monitorDeviceDOConvert2VO(m, monitorDeviceVO);
            return monitorDeviceOldVO;
        }).collect(Collectors.toList());
        return collect;

    }


    private List<MonitorDeviceVO> listAll() {

        final List<MonitorDeviceEntity> monitorDeviceEntityList = this.list();

        List<MonitorDeviceVO> voList = MonitorConvertor.INSTANCE.monitorDeviceDOListConvert2VOList(monitorDeviceEntityList);

        return voList;
    }


    private DeviceService deviceService;

    @Autowired
    public void setDeviceService(DeviceService deviceService) {
        this.deviceService = deviceService;
    }


    private AlarmRecordService alarmRecordService;

    @Autowired
    public void setAlarmRecordService(AlarmRecordService alarmRecordService) {
        this.alarmRecordService = alarmRecordService;
    }

}
